﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using BeachRunner.Utility;
using BeachRunner.Tileengine;

namespace BeachRunner.Entity
{
    /// <summary>
    /// Ein allgemeines Spielentity
    /// 
    /// Bietet:
    ///     - Kollisionserkennung sowie Kollisionsauflösung
    ///     - einfache Kinematik
    ///     
    /// Autor: OLD (Mattis Hänel)
    /// </summary>
    public class Entity
    {
        /// <summary>
        /// Die Position des Entitys ( in Pixel )
        /// </summary>
        protected Vector2 position;
        /// <summary>
        /// Macht die Position des Entitys zugänglich. ( in Pixel )
        /// </summary>
        public Vector2 Position
        {
            get { return position; }
            set { position = value; }
        }

        /// <summary>
        /// Die Geschwindigkeit des Entitys ( in Pixel / Tick )
        /// </summary>
        protected Vector2 velocity;
        /// <summary>
        /// Macht die Geschwindigkeit des Entitys zugänglich. ( in Pixel / Tick ) 
        /// </summary>
        public Vector2 Velocity
        {
            get { return velocity; }
            set { velocity = value; }
        }

        /// <summary>
        /// Die Grenzgeschwindigkeit des Entitys ( in Pixel / Tick )
        /// </summary>
        protected Vector2 maximalVelocity;
        /// <summary>
        /// Macht die Grenzgeschwindigkeit des Entitys zugänglich. ( in Pixel / Tick ) 
        /// </summary>
        public Vector2 MaximalVelocity
        {
            get { return maximalVelocity; }
            set { maximalVelocity = value; }
        }

        /// <summary>
        /// Die Beschleunigung des Entitys ( in Pixel / Tick ^ 2 ) 
        /// </summary>
        protected Vector2 acceleration;
        /// <summary>
        /// Macht die Beschleunigung des Entitys zugänglich. ( in Pixel / Tick ^ 2 ) 
        /// </summary>
        public Vector2 Acceleration
        {
            get { return acceleration; }
            set { acceleration = value; }
        }

        /// <summary>
        /// Die Reibung die das Entitys durch seine Umgebung erfährt ( in Pixel / Tick ^ 2 )
        /// </summary>
        protected Vector2 friction;
        /// <summary>
        /// Macht die Reibung die das Entitys durch seine Umgebung erfährt zugänglich. ( in Pixel / Tick ^ 2 )
        /// </summary>
        public Vector2 Friction
        {
            get { return friction; }
            set { friction = value; }
        }

        /// <summary>
        /// Wieviel Energie kinetische behält das Entity nach einer Reflektion an seiner Umgebung
        /// 0.0 - Lässt das Entity an Hindernissen stopen
        /// 1.0 - Entity behält seine volle geschwindigkeit, ändert aber seine Richtung ( Reflektion )
        /// </summary>
        protected Vector2 bounciness;
        /// <summary>
        /// Wieviel kinetische Energie behält das Entity nach einer Reflektion an seiner Umgebung
        /// 0.0 - Lässt das Entity an Hindernissen stopen
        /// 1.0 - Entity behält seine volle geschwindigkeit, ändert aber seine Richtung ( Reflektion )
        /// </summary>
        public Vector2 Bounciness
        {
            get { return bounciness; }
            set { bounciness = value; }
        }

        /// <summary>
        /// Wie schnell muss das Entity mindestens sein um nicht seine gesamte kinetische Energie
        /// an sein Hinderniss abzugeben.
        /// </summary>
        protected float minBounceVelocity;
        /// <summary>
        /// Wie schnell muss das Entity mindestens sein um nicht seine gesamte kinetische Energie
        /// an sein Hinderniss abzugeben.
        /// </summary>
        public float MinBounceVelocity
        {
            get { return minBounceVelocity; }
            set { minBounceVelocity = value; }
        }

        /// <summary>
        /// die Breite des Entitys ( in Pixel )
        /// </summary>
        protected int width;
        /// <summary>
        /// die Höhe des Entity ( in Pixel )
        /// </summary>
        protected int height;

        /// <summary>
        /// Offset auf Entity.Position zum positionieren der Entitygrafik 
        /// </summary>
        protected Vector2 drawOffset;
        /// <summary>
        /// Offset auf Entity.Position zum positionieren der Entitygrafik 
        /// </summary>
        public Vector2 DrawOffset
        {
            get { return drawOffset; }
            set { drawOffset = value; }
        }

        /// <summary>
        /// Gibt an ob das Entity steht
        /// </summary>
        protected bool standing;
        /// <summary>
        /// Gibt an ob das Entity steht
        /// </summary>
        public bool Standing
        {
            get { return standing; }
        }

        /// <summary>
        /// Wie sieht es mit der Kollision in der nächsten Gameloopiteration aus?
        /// </summary>
        protected TraceResult traceResult;
        /// <summary>
        /// Wie sieht es mit der Kollision in der nächsten Gameloopiteration aus?
        /// </summary>
        public TraceResult TraceResult
        {
            get { return traceResult; }
        }

        /// <summary>
        /// Welche Ebene soll zur Kollisionserkennung verwendet werden?
        /// </summary>
        protected Layer collisionLayer;
        /// <summary>
        /// Welche Ebene soll zur Kollisionserkennung verwendet werden?
        /// </summary>
        public Layer CollisionLayer
        {
            get { return collisionLayer; }
            set { collisionLayer = value; }
        }

        /// <summary>
        /// Das zu verwendende AnimationsSet
        /// </summary>
        protected AnimationSet animationSet;
        /// <summary>
        /// Das zu verwendende AnimationsSet
        /// </summary>
        public AnimationSet AnimationSet
        {
            get { return animationSet; }
        }

        /// <summary>
        /// Das EntitySet zu dem das Entity gehört.
        /// </summary>
        protected EntitySet entitySet;

        private Vector2 positionDraw;

        /// <summary>
        /// Erstellt ein neues Entity
        /// </summary>
        /// <param name="entitySet">Das zugehörige EntitySet</param>
        /// <param name="position">Die Startposition</param>
        /// <param name="width">Die Breite ( in Pixel )</param>
        /// <param name="height">Die Höhe ( in Pixel )</param>
        /// <param name="animationSet">Das Entityset dem das Entity angehörten soll</param>
        /// <param name="collisionLayer">Die zu verwendende Kollisionsebene</param>
        public Entity(EntitySet entitySet, Vector2 position, int width, int height, AnimationSet animationSet, Layer collisionLayer )
        {
            this.entitySet = entitySet;
            this.position = position;
            this.width = width;
            this.height = height;
            this.animationSet = animationSet;
            this.collisionLayer = collisionLayer;

            this.velocity = Vector2.Zero;
            this.acceleration = Vector2.Zero;
            this.friction = new Vector2(0.1f, 0.0f);
            this.bounciness = new Vector2(0.5f, 0.5f);
            this.minBounceVelocity = 2.5f;

            this.positionDraw = Vector2.Zero;
        }

        /// <summary>
        /// Führt die Logik des Entitys aus
        /// </summary>
        virtual public void Update()
        {
            velocity.Y += Game1.Gravity;
            velocity.X = CalculateNewVelocity(velocity.X, acceleration.X, friction.X, maximalVelocity.X);
            velocity.Y = CalculateNewVelocity(velocity.Y, acceleration.Y, friction.Y, maximalVelocity.Y);

            traceResult = Map.trace(position, velocity, width, height, collisionLayer);
            HandleTraceResult(traceResult);

            animationSet.Update();
        }

        /// <summary>
        /// Berechnet die Geschwindigkeit in der Nächsten Gameloopiteration
        /// </summary>
        /// <param name="velocity">momentane Geschwindigkeit</param>
        /// <param name="acceleration">momentane Beschleunigung</param>
        /// <param name="friction">moemntane Reibung</param>
        /// <param name="maximum">Maximalgeschwindigkeit</param>
        /// <returns></returns>
        public float CalculateNewVelocity( float velocity, float acceleration, float friction, float maximum )
        {
            if (acceleration != 0)
            {
                velocity += acceleration;
            }
            else if (friction > 0)
            {
                if (velocity - friction > 0)
                {
                    velocity -= friction;
                }
                else if (velocity + friction < 0)
                {
                    velocity += friction;
                }
                else
                {
                    velocity = 0;
                }
            }
            return MathEx.Limit( velocity, -maximum, maximum );
        }

        /// <summary>
        /// Behandelt die Kollision
        /// </summary>
        /// <param name="result">Das TraceResult der letzten Iteration</param>
        virtual public void HandleTraceResult(TraceResult result)
        {
            standing = false;

            if (traceResult.collisionX)
            {
                if (bounciness.X > 0 && Math.Abs(velocity.X) > minBounceVelocity)
                {
                    velocity.X *= -bounciness.X;
                }
                else
                {
                    velocity.X = 0;
                }
            }

            if (traceResult.collisionY)
            {
                if (bounciness.Y > 0 && Math.Abs(velocity.Y) > minBounceVelocity)
                {
                    velocity.Y *= -bounciness.Y;
                }
                else
                {
                    if (velocity.Y > 0)
                    {
                        standing = true;
                    }
                    velocity.Y = 0;
                }
            }

            position = traceResult.resPosition;
        }

        /// <summary>
        /// Zeichnet das Entity
        /// </summary>
        /// <param name="spriteBatch">Das Spriteset auf dem gezeichent werden soll</param>
        /// <param name="offset">Der Offset mit dem gezeichnet werden soll (Scrolling)</param>
        virtual public void Draw(SpriteBatch spriteBatch, Vector2 offset)
        {
            positionDraw = (position + drawOffset + offset);
            spriteBatch.Draw(animationSet.Texture, positionDraw, animationSet.CurrentRectangle, Color.White);
        }
    }
}
